﻿//-----------------------------------------------------------------------------------------------------------
// Wbfs Engine (WBFSe) Project by Omega Frost 
// http://wbfse.codeplex.com/
//
// WBFSe is Licensed under the terms of the 
// Microsoft Reciprocal License (Ms-RL)
//-----------------------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using WBFSe.IO.Drives;
using WBFSe.OS;
using System.IO;
using System.Runtime.InteropServices;

namespace WBFSe.IO.Streams
{
    /// <summary>
    /// A device stream bound to a sector table
    /// </summary>
    class WbfsDriveDiscStream : IWbfsStream
    {
        // Variables

        private IntPtr filePtr;
        private byte[] zero;
        private ushort[] sectors;
        private int sectorSize;
        private long length;
        private long size;

        // Events

        /// <summary>
        /// Indicates that the stream is closing
        /// </summary>
        public event StreamClosingDelegate Closing;

        // Properties

        /// <summary>
        /// Retrieves the file pointer
        /// </summary>
        public IntPtr FilePtr
        {
            get { return this.filePtr; }
        }

        /// <summary>
        /// Retrieves the stream length
        /// </summary>
        /// <exception cref="System.IOException">On operation failure</exception>
        public long Length
        {
            get { return this.length; }
        }

        /// <summary>
        /// Retrieves the physical size
        /// </summary>
        /// <exception cref="System.IOException">On operation failure</exception>
        public long Size
        {
            get { return this.size; }
        }


        // Methods

        /// <summary>
        /// Constructor
        /// </summary>
        /// <param name="drive">Wbfs drive</param>
        /// <param name="sectors">Sectors</param>
        /// <param name="length">The disc length</param>
        public WbfsDriveDiscStream(WbfsDrive drive, ushort[] sectors, long length)
        {
            this.filePtr = Native.CreateFile(@"\\.\" + drive.Name[0] + ':', 
                FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, 
                FileMode.Open, FileAttrib.None, IntPtr.Zero);

            if(this.filePtr == Native.InvalidHandle)
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }

            this.sectorSize = drive.WbfsSectorSize;
            this.sectors = sectors;
            this.length = length;
            this.size = (long)sectors.Length *
                drive.WbfsSectorSize;

            this.zero = new byte[drive.WbfsSectorSize];
            Array.Clear(this.zero, 0, drive.WbfsSectorSize);
        }

        /// <summary>
        /// Reads data from the stream
        /// </summary>
        /// <param name="pMem">Unmanaged memory</param>
        /// <param name="offset">Offset from pMem</param>
        /// <param name="position">Destination position</param>
        /// <param name="count">Number of bytes to be read</param>
        /// <returns>Number of bytes read</returns>
        /// <exception cref="System.IOException">On operation failure</exception>
        public uint Read(IntPtr pMem, int offset, long position, int count)
        {
            int discsecstart = (int)(position / this.sectorSize);
            int discsecend = (int)((position + count) / this.sectorSize);
            int discoffset = (int)(position % this.sectorSize);

            int read = 0;
            if(discoffset != 0)
            {
                int toread = Math.Min(this.sectorSize - discoffset, count);
                ushort drivesec = this.sectors[discsecstart];

                if (drivesec == 0)
                {
                    Marshal.Copy(this.zero, 0, IntPtr.Add(pMem, offset), toread);
                }
                else
                {
                    long drivepos = (long)drivesec * this.sectorSize;
                    ReadRaw(pMem, offset, drivepos + discoffset, toread);
                }

                offset += toread;
                read += toread;
                discsecstart++;
            }

            while(discsecstart < discsecend)
            {
                ushort drivesec = this.sectors[discsecstart];

                if (drivesec == 0)
                {
                    Marshal.Copy(this.zero, 0, IntPtr.Add(pMem, offset), this.sectorSize);
                }
                else
                {
                    long drivepos = (long)drivesec * this.sectorSize;
                    ReadRaw(pMem, offset, drivepos, this.sectorSize);
                }

                offset += this.sectorSize;
                read += this.sectorSize;
                discsecstart++;
            }

            if (read < count)
            {
                int toread = count - read;
                ushort drivesec = this.sectors[discsecstart];

                if (drivesec == 0)
                {
                    Marshal.Copy(this.zero, 0, IntPtr.Add(pMem, offset), toread);
                }
                else
                {
                    long drivepos = (long)drivesec * this.sectorSize;
                    ReadRaw(pMem, offset, drivepos, toread);
                }

                read += toread;
            }

            return (uint)read;
        }

        /// <summary>
        /// Writes data from the stream
        /// </summary>
        /// <param name="pMem">Unmanaged memory</param>
        /// <param name="offset">Offset from pMem</param>
        /// <param name="position">Destination position</param>
        /// <param name="count">Number of bytes to be written</param>
        /// <returns>Number of bytes written</returns>
        /// <exception cref="System.IOException">On operation failure</exception>
        public uint Write(IntPtr pMem, int offset, long position, int count)
        {
            int discsecstart = (int)(position / this.sectorSize);
            int discsecend = (int)((position + count) / this.sectorSize);
            int discoffset = (int)(position % this.sectorSize);

            int written = 0;
            if (discoffset != 0)
            {
                int towrite = Math.Min(this.sectorSize - discoffset, count);
                ushort drivesec = this.sectors[discsecstart];

                if (drivesec == 0)
                    throw new IOException("Sector not allocated");

                long drivepos = (long)drivesec * this.sectorSize;
                WriteRaw(pMem, offset, drivepos + discoffset, towrite);

                offset += towrite;
                written += towrite;
                discsecstart++;
            }

            while (discsecstart < discsecend)
            {
                ushort drivesec = this.sectors[discsecstart];

                if (drivesec == 0)
                    throw new IOException("Sector not allocated");

                long drivepos = (long)drivesec * this.sectorSize;
                WriteRaw(pMem, offset, drivepos, this.sectorSize);

                offset += this.sectorSize;
                written += this.sectorSize;
                discsecstart++;
            }

            if (written < count)
            {
                int towrite = count - written;
                ushort drivesec = this.sectors[discsecstart];

                if (drivesec == 0)
                    throw new IOException("Sector not allocated");

                long drivepos = (long)drivesec * this.sectorSize;
                WriteRaw(pMem, offset, drivepos, towrite);

                written += towrite;
            }

            return (uint)written;
        }

        /// <summary>
        /// Reads data directly from disc
        /// </summary>
        /// <param name="pMem">Unmanaged memory</param>
        /// <param name="offset">Offset from pMem</param>
        /// <param name="position">Destination position</param>
        /// <param name="count">Number of bytes to be written</param>
        /// <returns>Number of bytes written</returns>
        /// <exception cref="System.IOException">On operation failure</exception>
        private uint ReadRaw(IntPtr pMem, int offset, long position, int count)
        {
            if (!Native.SetFilePointerEx(this.filePtr, position, out position, SeekOrigin.Begin))
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }

            uint read = (uint)count;
            if (!Native.ReadFile(this.filePtr, IntPtr.Add(pMem, offset), read, out read, IntPtr.Zero))
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }

            return read;
        }

        /// <summary>
        /// Writes data directly to disc
        /// </summary>
        /// <param name="pMem">Unmanaged memory</param>
        /// <param name="offset">Offset from pMem</param>
        /// <param name="position">Destination position</param>
        /// <param name="count">Number of bytes to be written</param>
        /// <returns>Number of bytes written</returns>
        /// <exception cref="System.IOException">On operation failure</exception>
        private uint WriteRaw(IntPtr pMem, int offset, long position, int count)
        {
            if (!Native.SetFilePointerEx(this.filePtr, position, out position, SeekOrigin.Begin))
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }

            uint write = (uint)count;
            if (!Native.WriteFile(this.filePtr, IntPtr.Add(pMem, offset), write, out write, IntPtr.Zero))
            {
                int error = Marshal.GetLastWin32Error();
                throw new WbfsIOException(error);
            }

            return write;
        }

        /// <summary>
        /// Sets a region as empty
        /// </summary>
        /// <param name="position">Destination position</param>
        /// <param name="count">Number of bytes to set</param>
        /// <returns>Number of bytes set</returns>
        /// <exception cref="System.IOException">On operation failure</exception>
        public void WriteEmpty(long position, int count)
        {
            // Do nothing...
        }

        /// <summary>
        /// Closes the stream, this method can fail silently
        /// </summary>
        public void Close()
        {
            if (this.Closing != null)
                this.Closing(this);

            Native.CloseHandle(this.filePtr);
            this.filePtr = Native.InvalidHandle;
        }

        /// <summary>
        /// Disposes the stream
        /// </summary>
        public void Dispose()
        {
            this.Close();
        }
    }
}
